package terra.model.object;

import java.awt.geom.Line2D;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;

import terra.Config;
import terra.database.DatabaseFactory;
import terra.managers.DefManager;
import terra.managers.ItemManager;
import terra.managers.MapManager;
import terra.managers.ThreadPoolManager;
import terra.model.Inventory;
import terra.model.ShortCut;
import terra.model.World;
import terra.model.object.interfaces.Attackable;
import terra.model.object.interfaces.Attacker;
import terra.model.object.interfaces.Interactable;
import terra.model.object.item.Item;
import terra.model.object.item.WeaponItem;
import terra.model.object.item.WearItem;
import terra.model.object.knownlist.PlayerKnownList;
import terra.model.object.map.AbstractMapObject;
import terra.model.object.map.GroundObject;
import terra.model.object.map.MapObject;
import terra.model.object.stat.PlayerStat;
import terra.network.PlayerHandler;
import terra.network.packet.Packet;
import terra.network.packet.client.PlayerControl;
import terra.network.packet.server.CharInfo;
import terra.network.packet.server.ObjectMove;
import terra.network.packet.server.PlayerInfo;
import terra.network.packet.server.Resurection;
import terra.network.packet.server.battle.Attack;

public class Player extends LiveObject implements Attacker, Interactable {

	private static final long serialVersionUID = 1L;
	private transient PlayerHandler handler;
	long lastBreath = System.currentTimeMillis();
	public Inventory inv;
	public ShortCut[] shortcuts = new ShortCut[10];
	public ArrayList<Integer> knownRecipes = new ArrayList<Integer>();

	boolean moveLeft = true;
	public boolean sendPlayerInfo = false;
	private byte moveType;
	boolean canJump = true;
	transient AbstractMapObject intersect;
	transient int hitTimer = 0;
	boolean sendMove = false;
	public PlayerControl control = new PlayerControl();
	public CharInfo charInfo = new CharInfo();
	public PlayerInfo playerInfo = new PlayerInfo();
	PlayerStat stat;
	private byte anim_type = ObjectMove.ANIMATION_STAND;
	private long lastConstructTime;
		
	public Player(int objectId, int x, int y, int w, int h,
			PlayerHandler handler) {
		super(objectId);
		setHandler(handler);
		setName(getHandler().getName());
		inv = new Inventory(20, this);
		setX(x);
		setY(y);
		setOldX(x);
		setOldY(y);
		setWidth(w);
		setHeight(h);
		setJumpStartSpeed(6.5f);
		getKnownList().setAutoUpdate(500);
		World.getInstance().addObject(getObjectId(), this);
	}

	@Override
	public void update(int delta) {
		if (isRemove())
			return;
		
		float oldX = getX();
		float oldY = getY();
		setHeading((float) -Math.toDegrees(Math.atan2(getX() - control.mouseX,
				getY() - control.mouseY)));
		hitTimer -= delta;
		byte newMoveType = checkMoveType(0, 0);
		byte oldMoveType = getMoveType();
		if (newMoveType == MOVE_ON_GROUND
				|| newMoveType == MOVE_ON_LADDER
				|| (newMoveType == MOVE_ON_WATER && getMoveType() != MOVE_ON_WATER)
				&& getJumpTimer() < System.currentTimeMillis())
			canJump = true;
		checkBreath(newMoveType);
		setMoveType(newMoveType);

		int speed = (int) ((getSpeed() / 1000f) * delta);
		float diffX = 0, diffY = 0;
		if (!isDead() && control != null) {
			if (control.RIGHT){
				diffX = speed;
				setLeft(false);
			} else 	if (control.LEFT) {
				diffX = -speed;
				setLeft(true);
			}
			if (control.UP || (getMoveType() == MOVE_UNDER_WATER && control.JUMP)){
				diffY = -speed;
			} else if (control.DOWN){
				diffY = speed;	
			}

			if (Config.freeMove) {
				setX(getX() + diffX);
				setY(getY() + diffY);
			} else {
				if (getMoveType() == MOVE_ON_WATER
						|| getMoveType() == MOVE_ON_GROUND
						|| getMoveType() == MOVE_ON_LADDER)
					if (control.JUMP
							&& getJumpTimer() < System.currentTimeMillis()
							&& canJump) {
//						System.out.println("JUMP!");
						setYacceleration(-getJumpStartSpeed());
						setJumpTimer(getJumpTime() + System.currentTimeMillis());
						canJump = false;
					}
				if (getMoveType() == MOVE_ON_GROUND
						|| getMoveType() == MOVE_NONE)
					diffY = 0;
				if (getMoveType() == MOVE_UNDER_WATER) {
					diffX /= 2;
					diffY /= 2;
				}
				if (getMoveType() == MOVE_ON_WATER)
					diffX /= 2;
				if (getMoveType() == MOVE_UNDER_WATER
						|| (getMoveType() == MOVE_ON_LADDER && getJumpTimer() < System
								.currentTimeMillis())/*
													 * !GameState.getInstance().
													 * isPressed
													 * (Input.KEY_SPACE)
													 */)
					setYacceleration(0);
				if (getMoveType() == MOVE_NONE && oldMoveType == MOVE_ON_WATER
						&& canJump)
					setYacceleration(0);

				if (!isCollision2(getX() + diffX, getY()))
					setX(getX() + diffX);
				if (!isCollision2(getX(), getY() + diffY)) {
					if (getMoveType() == MOVE_ON_WATER) {
						if ((checkMoveType(0, diffY)) == MOVE_ON_WATER) {
							setY(getY() + diffY);
						}
					} else {
						setY(getY() + diffY);
					}
				}
				if ((getMoveType() == MOVE_ON_WATER && !(control != null && control.JUMP))
						|| ((getMoveType() == MOVE_ON_LADDER && getJumpTimer() < System
								.currentTimeMillis()))) {

				} else
					updateGravity(delta);
			}
			Object target = findTarget();
			if (control.LMB) {
				if (control.CTRL
						|| (target instanceof GroundObject || target instanceof Attackable))
					hit();
				control.LMB = false;
			}
			if (control.RMB) {
				if (target instanceof Interactable)
					((Interactable) target).onClick(0, this);
				else if (target == null || target instanceof MapObject){
					construct((control.mouseX) / Config.tileSize, 
							(control.mouseY) / Config.tileSize);
				}
//					construct(Math.round(control.mouseX / Config.tileSize), Math.round(control.mouseY
//							/ Config.tileSize));
//					System.out.println());
				control.RMB = false;
			}
		} else {
			updateGravity(delta);
		}

		if (anim_type != ObjectMove.ANIMATION_ATTACK){
//			if (getMoveType() == MOVE_ON_LADDER && positionChanged())
//				anim_type = ObjectMove.ANIMATION_LADDER;	
			if (oldX != getX()){
				if (getMoveType() == MOVE_ON_LADDER)
					anim_type = ObjectMove.ANIMATION_LADDER;
				else
					anim_type = ObjectMove.ANIMATION_MOVE;
			}
			else if (oldY < getY()){
				if (getMoveType() == MOVE_ON_LADDER)
					anim_type = ObjectMove.ANIMATION_LADDER;
				else
					anim_type = ObjectMove.ANIMATION_JUMP;
			}
			else if (oldY > getY()){
				if (getMoveType() == MOVE_ON_LADDER)
					anim_type = ObjectMove.ANIMATION_LADDER;
				else
					anim_type = ObjectMove.ANIMATION_FALL;
			}
			else 
				anim_type = ObjectMove.ANIMATION_STAND;
		}
		int oldMapIndex = MapManager.getInstance().getMapIndex(
				(int) (getOldX() / Config.tileSize));
		if (oldMapIndex != getMapIndex()) {
			MapManager.getInstance().getMap(getMapIndex()).addObject(this);
			MapManager.getInstance().getMap(oldMapIndex).removeObject(this);
		}
		needUpdate();
		for (GameObject obj : getKnownList().getKnownObjects().values()) {
			if (obj instanceof Player)
				continue;
			if (obj instanceof Arrow)
				continue;
			if (!obj.positionChanged())
				continue;
			send(obj.getObjectMove());
		}

		if (sendPlayerInfo) {
			sendInfo(true);
			sendPlayerInfo = false;
		}
		if (inv.changed)
			inv.inventoryChanged();
//		if (positionChanged()) {
			checkTouching();
			sendObjectMove();
//		}
	}

	private Object findTarget() {
		for (GameObject obj : getKnownList().getKnownObjects().values()) {
			if (obj.intersects(control.mouseX, control.mouseY, 2, 2)) {
				return obj;
			}
		}
		return MapManager.getInstance().getTileFromTiles(
				control.mouseX / Config.tileSize,
				control.mouseY / Config.tileSize);
	}

	private void checkBreath(byte newMoveType) {
		if (isDead())
			return;
		long current = System.currentTimeMillis();
		if (getMoveType() == newMoveType) {
			if (newMoveType == MOVE_UNDER_WATER) {
				if (lastBreath < current) {
					if (getStat().getBreath() - 1 > 0)
						getStat().setBreath(getStat().getBreath() - 1);
					else {
						getStat().reduceHp(2);
						getStat().setBreath(0);
					}
					lastBreath = current + 500L;
				}
			} else {
				if (lastBreath < current
						&& getStat().getBreath() < getStat().getMaxBreath()) {
					getStat().setBreath(getStat().getBreath() + 2);
					if (getStat().getBreath() > getStat().getMaxBreath())
						getStat().setBreath(getStat().getMaxBreath());
					lastBreath = current + 500L;
				}
			}
		}
	}

	public void hit() {
		if (control == null)
			return;
		if (hitTimer > 0)
			return;
		else
			hitTimer = (int) (1000 / getStat().getAttackRate());
		Attack attack = new Attack();
		attack.oId = getObjectId();
		double angle2 = -Math.atan2(getX() + getWidth() / 2 - control.mouseX,
				getY() + getHeight() / 2 - control.mouseY);
		double pointX = (float) (Config.tileSize * Math.cos(angle2
				- Math.toRadians(90)));
		double pointY = (float) (Config.tileSize * Math.sin(angle2
				- Math.toRadians(90)));
		// System.out.println("HIT!");
		if (inv.getWeapon() != null
				&& inv.getWeapon().weaponType == WeaponItem.TYPE_RANGE) {
			Arrow arrow = new Arrow(World.getNextId());
			arrow.angle = Math.toDegrees(angle2);
			arrow.setX((float) (getX() + pointX));
			arrow.setY((float) (getY() + pointY));
			arrow.setSpeed(512);
			arrow.setImage("arrow");
			arrow.owner = this;
			arrow.setWidth(Config.tileSize);
			arrow.setHeight(Config.tileSize);
			arrow.getKnownList().updateKnownObjects();
			World.getInstance().addObject(arrow.getObjectId(), arrow);
			arrow.broadcastInfo();
			broadcastPacket(attack, true);
			return;
		}
		Line2D hit = new Line2D.Float(getX() + getWidth() / 2, getY()
				+ getHeight() / 2, (float) (getX() + getWidth() / 2 + pointX),
				(float) (getY() + getHeight() / 2 + pointY));
		GameObject hited = null;
		for (GameObject obj : getKnownList().getKnownObjects().values()) {
			if (obj instanceof Attackable) {
				if (intersects((int) hit.getBounds().getMinX(), (int) hit
						.getBounds().getMinY(), (int) hit.getBounds()
						.getWidth(), (int) hit.getBounds().getHeight(),
						(int) obj.getX(), (int) obj.getY(), obj.getWidth(),
						obj.getHeight())) {
					hited = obj;
					break;
				}
			}
		}
		if (hited != null) {
			if (hited instanceof Attackable) {
				((Attackable) hited).onHit(this, getDamage());
				attack.targetOId = hited.getObjectId();
				broadcastPacket(attack, true);
				return;
			}
		}
		int minX = (int) hit.getBounds().getMinX() / Config.tileSize - 1;
		int maxX = (int) hit.getBounds().getMaxX() / Config.tileSize + 2;
		int minY = (int) hit.getBounds().getMinY() / Config.tileSize - 1;
		int maxY = (int) hit.getBounds().getMaxY() / Config.tileSize + 2;

		for (int x = minX; x <= maxX; x++) {
			for (int y = minY; y <= maxY; y++) {
				AbstractMapObject go = MapManager.instance.getTileFromTiles(x,
						y);
				if (go == null || go.getType() != AbstractMapObject.TYPE_GROUND)
					continue;
				if (intersects((int) hit.getBounds().getMinX(), (int) hit
						.getBounds().getMinY(), (int) hit.getBounds()
						.getWidth(), (int) hit.getBounds().getHeight(),
						go.getX(), go.getY(), Config.tileSize, Config.tileSize)) {
					((GroundObject) go).hit(getDamage());
					broadcastPacket(attack, true);
					return;
				}
			}
		}

	}

	private byte checkMoveType(float diffX, float diffY) {
		int x1 = (int) ((getX() + diffX) / Config.tileSize);
		int x2 = (int) ((getX() + getWidth() + diffX) / Config.tileSize);
		int y1 = (int) ((getY() + diffY) / Config.tileSize);
		int y2 = (int) ((getY() + getHeight() + diffY) / Config.tileSize);
		if (x1 < 0 && x1 % Config.tileSize != 0)
			x1--;
		if (x2 < 0 && x2 % Config.tileSize != 0)
			x2--;
		for (int x = x1; x <= x2; x++)
			for (int y = y1; y <= y2; y++) {
				AbstractMapObject go = MapManager.instance.getTileFromTiles(x,
						y);
				if (go == null || go.getType() == MapObject.TYPE_AIR)
					continue;
				// if (asd <= 0)
				// System.out.println("intersect " + go.getX() + "_"+ go.getY()
				// + "   type "+ go.getType() + "  canMove?" +go.canMove());
				// если блок = вода
				// надо проверить, покрывает ли блок воды с его массой, высоту
				// игрока полностью.
				intersect = go;
				if (go.getType() == MapObject.TYPE_WATER) {
					if (go.getMass() < 0.1f)
						continue;
					float diff = getY() - go.getY() + go.getMass()
							* Config.tileSize;
					// if (asd <= 0)
					// System.out.println("Y difference " + diff + " asdas " +
					// go.getMass());
					if (diff >= Config.tileSize)
						return MOVE_UNDER_WATER;
					// else if (diff >= 0)
					else if (diff >= 0)
						return MOVE_ON_WATER;
					// else if (go.getType() == MapObject.TYPE_GROUND)
					// return MOVE_ON_GROUND;
				} else if (go.getType() == MapObject.TYPE_GROUND) {
					if (((GroundObject) go).isLadder)
						return MOVE_ON_LADDER;
					else if (!go.canMove())
						return MOVE_ON_GROUND;
					// else
					// return MOVE_NONE;
					// else if (go.getNeighbor(MapObject.UP).getType() ==
					// MapObject.TYPE_GROUND)
					// return MOVE_ON_GROUND;
				}
				// }

			}
		return MOVE_NONE;
	}

	@Override
	public int getDamage() {
		if (inv.getWeapon() != null)
			return inv.getWeapon().damage;
		return 5;
	}

	@Override
	public void objectChanged(GObject object) {
	}

	public void setMoveType(byte moveType) {
		this.moveType = moveType;
	}

	public byte getMoveType() {
		return moveType;
	}

	@Override
	public void onClick(int button, Player player) {

	}


	@Override
	public PlayerStat getStat() {
		if (stat == null)
			stat = new PlayerStat(this);
		return (PlayerStat) stat;
	}

	@Override
	public void onKill(Attackable target) {
		if (target == this)
			return;
		getStat().addExp(((LiveObject) target).getStat().getMaxHp() / 10);
	}

	public void registerShortCut(ShortCut sc, int index) {
		shortcuts[index] = sc;
	}

	public void useShorCut(int index) {
		ShortCut sc = shortcuts[index];
		if (sc == null)
			return;
		if (sc.getType() == ShortCut.TYPE_ITEM) {
			Item it = inv.getItemByObjectId(sc.getId());
			if (it == null)
				registerShortCut(null, index);
			else
				useItem(it);
		}
	}

	public void useItem(Item it) {
		if (it.getDef().isConstruct) {
			inv.setConstructable(it);
			return;
		}
		if (it instanceof WearItem) {
			WearItem wear = (WearItem) it;
			if (!wear.isWeared)
				inv.wearItem(wear, true);
			else
				inv.unWearItem(wear, true);
		} else if (it.getDef().instance != null) {
			inv.dropItem((int) getX(), (int) getY(), it.getObjectId(), 1);
			System.out.println("DROP Item " + it);
		}
	}

	public void useItem(int objectId) {
		Item it = inv.getItemByObjectId(objectId);
		if (it != null)
			useItem(it);
	}

	public void checkShortCuts() {
		for (int i = 0; i < shortcuts.length; i++) {
			ShortCut sc = shortcuts[i];
			if (sc == null)
				continue;
			if (sc.getType() == ShortCut.TYPE_ITEM)
				if (inv.getItemByObjectId(sc.getId()) == null)
					registerShortCut(null, i);
		}
	}

	public WeaponItem getWeapon() {
		if (inv != null && inv.getWeapon() != null)
			return inv.getWeapon();
		return ItemManager.arms;
	}

	/**
	 * Метод создания блока
	 * 
	 * @param координаты
	 *            блока
	 * @return код ошибки -1 - успешно создано. 0 - создать можно только на
	 *         пустом месте 1 - не выбран материал в панели быстрого доступа 2 -
	 *         невозможно создать блок, не соприкасающийся с твердым блоком. 3 -
	 *         Выбранного материала нет в инвентаре 4 - Невозможно построить
	 *         блок - место для постройки занято 5 - Дверь должна ставитьсяна
	 *         землю 6 - Ошибка постройки двери
	 */
	public int construct(int x, int y) {
		if (lastConstructTime+500 > System.currentTimeMillis()){
			return -2;
		}
		lastConstructTime = System.currentTimeMillis();
		
		int mapIndex = MapManager.getInstance().getMapIndex(x);
		int tileX = x - mapIndex * Config.mapWidth;
		AbstractMapObject tile = MapManager.getInstance()
				.getTileFromTiles(x, y);
		if (tile != null && tile.getType() != MapObject.TYPE_AIR)
			return 0;
		if (inv.constructable == null)
			return 1;
		Item item = inv.constructable;
		if (inv.getItemByItemId(item.getId()) == null)
			return 3;
		if (tile != null) {
			for (GameObject object : getKnownList().getKnownObjects().values())
				if (object.intersects(tile.getX(), tile.getY(),
						Config.tileSize-1, Config.tileSize-1))
					return 4;
		}
		if (item.getDef().constructGroundId != -1) {
			boolean haveGroundNeighbor = false;
			for (int i = 0; i < 4; i++) {
				if (tile != null) {
					if (tile.getNeighbor(i) != null
							&& tile.getNeighbor(i).getType() == MapObject.TYPE_GROUND)
						haveGroundNeighbor = true;
				} else {
					AbstractMapObject neighbor = AbstractMapObject.getNeighbor(
							tileX, y, mapIndex, i);
					if (neighbor != null
							&& neighbor.getType() == MapObject.TYPE_GROUND)
						haveGroundNeighbor = true;
				}
			}
			if (!haveGroundNeighbor)
				return 2;

			GroundObject constructed = new GroundObject(x * Config.tileSize, y
					* Config.tileSize, (short) tileX, (short) y,
					DefManager.groundDefs.get(item.getDef().constructGroundId),
					MapManager.getInstance().getMaps().get(mapIndex));
			constructed.setBackgroundId(tile == null ? -1 : tile.getId());
			World.getInstance().groundListener.checkNewGround(constructed);
			constructed.getMap().getMap()[constructed.getTileX()][constructed
					.getTileY()] = constructed;
			constructed.sendTileInfo();
		} else if (item.getDef().constructObjectId != -1) {
			if (DefManager.npcDefs.get(item.getDef().constructObjectId)
					.getStringProperty("instance").equalsIgnoreCase("door")) {
				
				AbstractMapObject neighbor = AbstractMapObject.getNeighbor(
						tileX, y+1, mapIndex, AbstractMapObject.DOWN);
				if (neighbor != null && neighbor.getType() != MapObject.TYPE_GROUND)
					return 5;
				neighbor = AbstractMapObject.getNeighbor(
						tileX, y, mapIndex, AbstractMapObject.DOWN);
				if ((neighbor != null && neighbor.getType() != MapObject.TYPE_AIR) ||
						(tile != null && tile.getType() != MapObject.TYPE_AIR))
					return 4;
				
				Door door = new Door(
				DefManager.npcDefs.get(item.getDef().constructObjectId));
				if (tile == null)
					tile = new MapObject(x * Config.tileSize, y
							* Config.tileSize, (short) tileX, (short) y,
							DefManager.groundDefs.get(-1),
							MapManager.getInstance().getMaps().get(mapIndex));
				
				for (GameObject object : getKnownList().getKnownObjects().values())
					if (object.intersects(tile.getX(), tile.getY(),
							Config.tileSize, Config.tileSize))
						return 4;
				
				if (!door.makeDoor(tile))
					return 6;
				World.getInstance().addObject(door.getObjectId(), door);
				door.insertStaticObject();
//				if (tile.getNeighbor(MapObject.DOWN)
//						.getNeighbor(MapObject.DOWN).getType() != MapObject.TYPE_GROUND)
//					return 5;
//				Door door = new Door(
//						DefManager.npcDefs.get(item.getDef().constructObjectId));
//				if (!door.makeDoor(tile))
//					return 6;
//				World.getInstance().addObject(door.getObjectId(), door);
//				// ObjectManager.addObject(door);
			}
		}
		if (item.getDef().isStackable)
			inv.changeCount(item.getId(), -1);
		else
			inv.removeItem(item);
		

		//inv.getItemByObjectId(item.getObjectId())
//		inv.getItemByItemId(item.getId()).addCount(-1);
//		if (inv.getItemByItemId(item.getId()).getCount() <= 0) {
//			inv.removeItem(item);
//			inv.constructable = null;
//			checkShortCuts();
//		}
		return -1;
	}

	public PlayerHandler getHandler() {
		return handler;
	}

	public void setHandler(PlayerHandler handler) {
		this.handler = handler;
	}

	public void sendInfo(boolean broadcast) {
		if (playerInfo == null)
			playerInfo = new PlayerInfo();
		playerInfo.x = getX();
		playerInfo.y = getY();
		playerInfo.w = getWidth();
		playerInfo.h = getHeight();
		playerInfo.breath = getStat().getBreath();
		playerInfo.maxBreath = getStat().getMaxBreath();
		playerInfo.exp = getStat().getExp();
		playerInfo.level = getStat().getLevel();
		playerInfo.con = getStat().getCon();
		playerInfo.str = getStat().getStr();
		playerInfo.dex = getStat().getDex();
		playerInfo.atk = (int) getStat().getAttackPower();
		playerInfo.def = (int) getStat().getDefenseRate();
		playerInfo.critChance = (int) getStat().getCriticalChance() * 100;
		playerInfo.critPower = (int) getStat().getCriticalPower();
		playerInfo.hp = getStat().getCurrentHp();
		playerInfo.maxHp = getStat().getMaxHp();
		playerInfo.oId = getObjectId();
		playerInfo.name = getName();
		getHandler().send(playerInfo);
		if (broadcast) {
			broadcastCharInfo();
		}
	}

	private void sendObjectMove() {
		broadcastPacket(getObjectMove(), true);
	}

	public void broadcastCharInfo() {
		broadcastPacket(getInfo(), false);
	}

	/**
	 * Отдаём пакет всем игрокам, находящимся в зоне видимости.
	 * 
	 * @param sendSelf
	 */
	public void broadcastPacket(Packet p, boolean sendSelf) {
		if (isRemove())
			return;
		for (Player player : getKnownList().getPlayers().values()) {
			player.getHandler().send(p);
		}
		if (sendSelf)
			send(p);
	}

	/** Отсылает пакет только своему клиенту */
	public void send(Packet packet) {
		if (isRemove())
			return;
		getHandler().send(packet);
	}

	public Packet getInfo() {
		if (charInfo == null)
			charInfo = new CharInfo();
		charInfo.x = getX();
		charInfo.y = getY();
		charInfo.w = getWidth();
		charInfo.h = getHeight();
		charInfo.breath = getStat().getBreath();
		charInfo.maxBreath = getStat().getMaxBreath();
		charInfo.exp = getStat().getExp();
		charInfo.level = getStat().getLevel();
		charInfo.con = getStat().getCon();
		charInfo.str = getStat().getStr();
		charInfo.dex = getStat().getDex();
		charInfo.hp = getStat().getCurrentHp();
		charInfo.maxHp = getStat().getMaxHp();
		charInfo.oId = getObjectId();
		charInfo.name = getName();
		return charInfo;
	}

	public PlayerKnownList getKnownList() {
		if (knownList == null)
			return (PlayerKnownList) (knownList = new PlayerKnownList(this));
		return (PlayerKnownList) knownList;
	}

	public void onDisconnect() {
		this.setRemove(true);
		savePlayer();
		for (GameObject obj : getKnownList().getKnownObjects().values())
			obj.getKnownList().removeKnownObject(this);
		getKnownList().removeAutoUpdate();
		getKnownList().removeAllKnownObjects();
		playerInfo = null;
		charInfo = null;
		control = null;
	}

	@Override
	public void die(Attacker killer) {
		super.die(killer);
		ThreadPoolManager.getInstance().scheduleGeneral(new Ressurect(), 5000L);
	}

	class Ressurect implements Runnable {

		@Override
		public void run() {
			setDead(false);
			getStat().setCurrentHp(getStat().getMaxHp());
			broadcastPacket(new Resurection(getObjectId(), getX(), getY()),
					true);
			needUpdate();
		}

	}

	public static Player loadPlayer(ResultSet rs, PlayerHandler playerHandler)
			throws SQLException {
		Player p = new Player(rs.getInt("objectId"), rs.getInt("x"),
				rs.getInt("y"), 16, 31, playerHandler);
		p.getStat().setCon(rs.getShort("con"));
		p.getStat().setStr(rs.getShort("str"));
		p.getStat().setDex(rs.getShort("dex"));
		p.getStat().setExp(rs.getLong("exp"));
		p.getStat().setLevel((byte) rs.getInt("level"));
		p.getStat().calcStats();
		p.inv.loadInventory();
		return p;
	}

	public void savePlayer() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("UPDATE characters SET level=?,exp=?,str=?,con=?,dex=?,x=?,y=? WHERE objectId=?")) {
			statement.setInt(1, getStat().getLevel());
			statement.setLong(2, getStat().getExp());
			statement.setInt(3, getStat().getStr());
			statement.setInt(4, getStat().getCon());
			statement.setInt(5, getStat().getDex());
			statement.setInt(6, (int) getX());
			statement.setInt(7, (int) getY());
			statement.setInt(8, getObjectId());
			statement.execute();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		inv.saveInventory();
	}

	public void insertPlayer() {
		try (Connection con = DatabaseFactory.getInstance().getConnection();
				PreparedStatement statement = con
						.prepareStatement("INSERT INTO characters (objectId,accountId,x,y,level,exp,str,con,dex) VALUES (?,?,?,?,?,?,?,?,?)");) {
			statement.setInt(1, getObjectId());
			statement.setInt(2, getHandler().getAccountId());
			statement.setInt(3, (int) getX());
			statement.setInt(4, (int) getY());
			statement.setInt(5, getStat().getLevel());
			statement.setLong(6, getStat().getExp());
			statement.setInt(7, getStat().getStr());
			statement.setInt(8, getStat().getCon());
			statement.setInt(9, getStat().getDex());
			statement.execute();
			statement.close();
		} catch (Exception e) {
			e.printStackTrace();
		}
		inv.saveInventory();
	}

	
	@Override
	public ObjectMove getObjectMove() {
		om.oId = getObjectId();
		om.x = getX();
		om.y = getY();
		om.heading = getHeading();
		om.num = getObjectMoveNum();
		om.left = this.isLeft();
		om.anim_type = anim_type;
		return om;
	}
}
